//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Linq;
using System.Data.Linq;
using System.Collections.Generic;
using System.Security.Principal;
using Mindscape.BackgroundMotion.Core;
using Mindscape.BackgroundMotion.Core.Model;
using Mindscape.BackgroundMotion.Core.Utilities;
using Mindscape.BackgroundMotion.Model;
using Mindscape.BackgroundMotion.Model.Security;
using Mindscape.BackgroundMotion.Website.Infrastructure;

namespace Mindscape.BackgroundMotion.Website.Controllers
{
  public sealed class MembershipController : ControllerBase, IMembershipController
  {
    public const string LoginPage = "/Login.aspx";
    private static readonly List<string> ModeratorPages = new List<string>();
    private static readonly List<string> SecurePages = new List<string>();

    /// <summary>
    /// Initializes the controller by assigning the moderated and secure pages to their associated collections
    /// </summary>
    static MembershipController()
    {
      ModeratorPages.Add("/moderate.aspx");
      ModeratorPages.Add("/admin.aspx");
      SecurePages.AddRange(ModeratorPages);
      SecurePages.Add("/add.aspx");
    }

    /// <summary>
    /// Returns the current MailSender instance
    /// </summary>
    public static IMailSender MailSender
    {
      get { return ServiceLocator.Get<IMailSender>(); }
    }

    /// <summary>
    /// Returns if the currently logged in member is a moderator of the system
    /// </summary>
    private bool IsModerator
    {
      get { return CurrentMember.IsModerator; }
    }

    /// <summary>
    /// Gets a <see ref="Member"/>.
    /// </summary>
    /// <param name="id">The unique identifier of the Member.</param>
    public Member GetMember(int id)
    {
      using (UnitOfWork.Begin())
      {
        return Repository<Member>.FindOne(m => m.Id == id);
      }
    }

    /// <summary>
    /// Gets the contributions which have been approved by the selected Member.
    /// </summary>
    /// <param name="member">The member.</param>
    /// <param name="pageNumber">The page number, used with the paging infrastructure.</param>
    public SearchResult GetApprovedContributions(Member member, int pageNumber)
    {
      if (pageNumber <= 0 )
      {
        pageNumber = 1;
      }
      
      SearchResult result = new SearchResult();
      result.PageNumber = pageNumber;
      result.PageSize = Constants.PageSize;
      int skip = (pageNumber - 1)*Constants.PageSize;

      using (UnitOfWork.Begin())
      {
        result.Contributions = Repository<Contribution>.FindAll()
          .Where(c => c.ApprovedById != null & c.ContributorId == member.Id)
          .OrderByDescending(c => c.AddedOn)
          .Skip(skip)
          .Take(Constants.PageSize).ToList();

        result.TotalResults = Repository<Contribution>.FindAll()
         .Where(c => c.ApprovedById != null & c.ContributorId == member.Id)
         .Count();
    
        UnitOfWork.Complete();
      }

      return result;
    }


    /// <summary>
    /// Gets the unapproved contributions.
    /// </summary>
    /// <param name="member">The member to get the unapproved contributions from.</param>
    /// <remarks>
    /// We don't paginate this result but we use the SearchResult response type to make it
    /// easier to add later if required.
    /// If the user is a moderator they see all unapproved items
    /// </remarks>
    /// <returns></returns>
    public SearchResult GetUnapprovedContributions(Member member)
    {
      SearchResult result = new SearchResult();

      using (UnitOfWork.Begin())
      {
        if (member.IsModerator)
        {
          result.Contributions = Repository<Contribution>.FindAll()
          .Where(c => c.ApprovedById == null)
          .OrderByDescending(c => c.AddedOn).ToList();

          result.TotalResults = Repository<Contribution>.FindAll()
         .Where(c => c.ApprovedById == null)
         .Count();
        }
        else
        {
          result.Contributions = Repository<Contribution>.FindAll()
          .Where(c => c.ApprovedById == null & c.ContributorId == member.Id)
          .OrderByDescending(c => c.AddedOn).ToList();

          result.TotalResults = Repository<Contribution>.FindAll()
         .Where(c => c.ApprovedById == null & c.ContributorId == member.Id)
         .Count();
        }

        UnitOfWork.Complete();
      }

      result.PageNumber = 1;
      result.PageSize = Constants.PageSize;

      return result;
    }

    /// <summary>
    /// Authenticates the current request
    /// </summary>
    public void AuthenticateRequest()
    {
      if (IsLoggedIn || AuthenticateFromCookie())
      {
        CreatePrincipal();
      }
      else if (IsSecurePage)
      {
        RedirectToLogin();

        return;
      }

      if (IsModeratorPage && !IsModerator)
      {
        RedirectToLogin();
      }
    }

    /// <summary>
    /// Authenticates the specified email address.
    /// </summary>
    /// <param name="emailAddress">The email address.</param>
    /// <param name="password">The password.</param>
    /// <param name="rememberMe">if set to <c>true</c> [remember me].</param>
    public void Authenticate(string emailAddress, string password, bool rememberMe)
    {
      Invariant.ArgumentNotEmpty(emailAddress, "emailAddress");
      Invariant.ArgumentNotEmpty(password, "password");

      using (UnitOfWork.Begin())
      {
        Member member;

        if (TryAuthenticateMember(emailAddress, password, out member))
        {
          Login(member);

          if (rememberMe)
          {
            CreatePersistentCookie(member);

            UnitOfWork.Complete();
          }

          ReturnToRequestedLocation();
        }
      }
    }

    /// <summary>
    /// Submits feedback from a member or anonymous user
    /// </summary>
    /// <param name="feedback">The feedback text to submit.</param>
    public void SubmitFeedback(string feedback)
    {
      string from = (CurrentMember==null)?"anonymous.user@backgroundmotion.com":CurrentMember.Email;
      MailSender.Send(from, "Feedback from Background Motion", feedback);
    }

    /// <summary>
    /// Authenticates a user from a determined HTTP cookie which may be attached to the current request
    /// </summary>
    /// <returns>true if authentication succeeds, else false</returns>
    private bool AuthenticateFromCookie()
    {
      string cookie = GetCookie(Constants.Cookie.RememberMe);

      if (!string.IsNullOrEmpty(cookie))
      {
        return AuthenticateFromCookie(cookie);
      }

      return false;
    }

    /// <summary>
    /// Authenticates a user from a presented HTTP cookie
    /// </summary>
    /// <param name="cookieValue">The value presented from the cookie</param>
    /// <returns>true if authentication succeeds, else false</returns>
    private bool AuthenticateFromCookie(string cookieValue)
    {
      Invariant.ArgumentNotEmpty(cookieValue, "cookieValue");

      using (UnitOfWork.Begin())
      {
        Member member = Repository<Member>.FindOne(m => m.HashCookie == cookieValue);

        if (member != null)
        {
          Login(member);

          return true;
        }
      }

      return false;
    }

    /// <summary>
    /// Logout out the user session
    /// </summary>
    public void Logout()
    {
      RemoveSession(Constants.Session.MemberId);
      RemoveCookie(Constants.Cookie.RememberMe);

      Principal = new GenericPrincipal(new GenericIdentity("Anonymous"), null);
    }

    /// <summary>
    /// Creates a new <see ref="Member"/> account.
    /// </summary>
    public void CreateAccount(Member member)
    {
      using (UnitOfWork.Begin())
      {
        member.Locked = false;
        member.LastActiveDate = DateTime.Now;
        member.LastUpdatedDate = DateTime.Now;

        Repository<Member>.Add(member);

        UnitOfWork.Complete();
      }
    }

    /// <summary>
    /// Logs a member in to the current session and set the last active date
    /// </summary>
    private void Login(Member member)
    {
      SetSession(Constants.Session.MemberId, member.Id);

      using (UnitOfWork.Begin())
      {
        member.LastActiveDate = DateTime.Now;
        Repository<Member>.Save(member);

        UnitOfWork.Complete();
      }
    }

    /// <summary>
    /// Creates a persistant HTTP cookie to be associated with a member for allowing the user to re-login on demand
    /// </summary>
    /// <param name="member">The selected member to create a cookie for</param>
    private void CreatePersistentCookie(Member member)
    {
      AddCookie(Constants.Cookie.RememberMe, member.HashCookie, DateTime.Now.AddYears(1));
    }

    /// <summary>
    /// Initiates a redirect to a previously stored location
    /// </summary>
    /// <remarks>
    /// Currently used by the login process
    /// </remarks>
    private void ReturnToRequestedLocation()
    {
      string returnTo = GetSession<string>(Constants.Session.ReturnUrl);

      if (returnTo != null)
      {
        RemoveSession(Constants.Session.ReturnUrl);

        RedirectTo(returnTo);
      }
      else
      {
        RedirectTo("~");
      }
    }

    /// <summary>
    /// Initiates a redirect to the login page
    /// </summary>
    private void RedirectToLogin()
    {
      SetSession(Constants.Session.ReturnUrl, RequestPathAndQuery);

      RedirectTo("~" + LoginPage);
    }

    /// <summary>
    /// Creates an application principal for the currently logged in member
    /// </summary>
    private void CreatePrincipal()
    {
      Principal = new BackgroundMotionPrincipal(new BackgroundMotionIdentity(
        GetMember(GetSession<int?>(Constants.Session.MemberId).Value)));
    }

    /// <summary>
    /// Checks if the currently requested view is a moderator only page
    /// </summary>
    private bool IsModeratorPage
    {
      get
      {
        foreach (string page in ModeratorPages)
        {
          if (RequestAbsolutePath.ToLower().EndsWith(page))
          {
            return true;
          }
        }

        return false;
      }
    }

    /// <summary>
    /// Checks if the currently requested view is a secure page
    /// </summary>
    private bool IsSecurePage
    {
      get
      {
        foreach (string page in SecurePages)
        {
          if (RequestAbsolutePath.ToLower().EndsWith(page))
          {
            return true;
          }
        }

        return false;
      }
    }

    /// <summary>
    /// Performs an authentication attempt against the member repository
    /// </summary>
    /// <param name="username">The username of the member</param>
    /// <param name="password">The password for the member</param>
    /// <param name="member">The member being returned from the repository if authentication succeeds</param>
    /// <returns>true if authentication is successful, else false</returns>
    private static bool TryAuthenticateMember(string username, string password, out Member member)
    {
      return Repository<Member>.TryFindOne(m => m.Email == username, out member) && member.Authenticate(password);
    }
  }
}